package com.zxh.util;

import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * http请求工具类
 *
 * @author zaqzaq
 *         2015年12月7日
 */
public class HttpPoolUtil {
    private static Logger logger = Logger.getLogger(HttpPoolUtil.class);
    private static HttpClientBuilder httpClientBuilder;
    private static Map<String, AtomicInteger> URL_POST_ERR_COUNT=new ConcurrentHashMap<String, AtomicInteger>();

    /**
     * 连接池配置
     */
    public static void init(){
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();

        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                //信任所有 https
                public boolean isTrusted(X509Certificate[] chain,
                                         String authType) throws CertificateException {
                    return true;
                }
            }).build();

            sslsf = new SSLConnectionSocketFactory(sslContext);

        } catch (Exception e) {
            logger.error("https请求初始化失败", e);
        }
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", plainsf)
                .register("https", sslsf)
                .build();

        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
        // 将最大连接数增加到http.pool.maxTotal
        cm.setMaxTotal(Constant.getPropertity("http.pool.maxTotal", 200));
        // 将每个路由基础的连接增加到http.pool.MaxPerRoute
        cm.setDefaultMaxPerRoute(Constant.getPropertity("http.pool.MaxPerRoute", 20));
        // 将目标主机的最大连接数增加到50
        //        HttpHost localhost = new HttpHost("http://www.baidu.com",80);
        //        cm.setMaxPerRoute(new HttpRoute(localhost), 50);
        //请求重试处理
        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                if (executionCount >= Integer.valueOf(Constant.getPropertity("http.post.maxError", 10))) {// 如果已经重试了maxError次，就放弃
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                    return true;
                }
                if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                    return false;
                }
                if (exception instanceof InterruptedIOException) {// 超时
                    return true;
                }
                if (exception instanceof UnknownHostException) {// 目标服务器不可达
                    return true;
                }
                if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
                    return true;
                }
                if (exception instanceof SSLException) {// ssl握手异常
                    return false;
                }

                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的，就再次尝试
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            }
        };

        RequestConfig defaultRequestConfig = RequestConfig.custom()
                .setSocketTimeout(Integer.valueOf(Constant.getPropertity("http.read.timeout", 1000)))
                .setConnectTimeout(Integer.valueOf(Constant.getPropertity("http.conn.timeout", 1000)))
                .setConnectionRequestTimeout(5000)
                .build();
        httpClientBuilder = HttpClients.custom().setDefaultRequestConfig(defaultRequestConfig)
                .setConnectionManager(cm)
                .setUserAgent(Constant.getPropertity("http.useragent"))
                .setRetryHandler(httpRequestRetryHandler);
    }

    /**
     * 组装http请求
     *
     * @param postUrl 请求的url
     * @param parm    参数
     * @return 字符体
     * @throws IOException
     */
    public static String postRetStr(String postUrl, NameValuePair... parm) {
        try {
            return EntityUtils.toString(post(postUrl, parm), "UTF-8");
        } catch (IOException e) {
            logger.error("请求：" + postUrl + " 异常", e);
            return "";
        }
    }

    public static String postRetStr(String postUrl, Header header, NameValuePair... parm) {
        try {
            return EntityUtils.toString(post(postUrl, header, parm), "UTF-8");
        } catch (IOException e) {
            logger.error("请求：" + postUrl + " 异常", e);
            return "";
        }
    }

    /**
     * 组装http请求
     *
     * @param postUrl 请求的url
     * @param parm    参数
     * @return 多媒体文体
     * @throws IOException
     */
    public static byte[] postRetMulti(String postUrl, NameValuePair... parm) {
        try {
            return EntityUtils.toByteArray(post(postUrl, parm));
        } catch (IOException e) {
            logger.error("请求多媒体文体：" + postUrl + " 异常", e);
            return null;
        }
    }

    public static String postRetStr(String postUrl, Header header, HttpEntity requestEntity) {
        try {
            return EntityUtils.toString(post(postUrl, header, requestEntity), "UTF-8");
        } catch (IOException e) {
            logger.error("请求：" + postUrl + " 异常", e);
            return null;
        }
    }

    private static HttpEntity post(String postUrl, NameValuePair... parm) {
        try {
            return post(postUrl, new BasicHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"), parm);
        } catch (UnsupportedEncodingException e) {
            logger.error("请求：" + postUrl + " 异常", e);
            return null;
        }
    }

    private static HttpEntity post(String postUrl, Header header, NameValuePair... parm) throws UnsupportedEncodingException {
        return post(postUrl, header, new UrlEncodedFormEntity(Arrays.asList(parm), "UTF-8"));
    }

    private static HttpEntity post(String postUrl, Header header, HttpEntity requestEntity) {
        CloseableHttpClient client = getClient();
        HttpPost request = new HttpPost(postUrl);
        request.addHeader(header);
        HttpEntity retVal = null;
        try {
            request.setEntity(requestEntity);
            CloseableHttpResponse response = client.execute(request);
            if (response.getStatusLine().getStatusCode() != 200) {
                logger.error(postUrl + "请求异常：statusCode=" + response.getStatusLine().getStatusCode());
                request.abort();
//                return post(postUrl, header, requestEntity);
            }
            retVal = response.getEntity();
        } catch (Exception e) {
            logger.error(postUrl + "请求异常", e);
            request.abort();
//            return post(postUrl, header, requestEntity);
        }

        logger.debug("response:" + retVal);

        return retVal;
    }

    public static String getRetStr(String getUrl, Header header, NameValuePair... parms) {
        try {
            return EntityUtils.toString(get(getUrl, header, parms), "UTF-8");
        } catch (IOException e) {
            logger.error("请求：" + getUrl + " 异常", e);
            return null;
        }
    }

    private static HttpEntity get(String getUrl, Header header, NameValuePair... parms) {

        CloseableHttpClient client = getClient();

        getUrl += URLEncodedUtils.format(Arrays.asList(parms), "utf-8");

        HttpGet request = new HttpGet(getUrl);
        request.addHeader(header);
        HttpEntity retVal = null;
        try {
            CloseableHttpResponse response = client.execute(request);
            if (response.getStatusLine().getStatusCode() != 200) {
                logger.error(getUrl + "请求异常：statusCode=" + response.getStatusLine().getStatusCode());
                request.abort();
//                return get(getUrl, header);
            }
            retVal = response.getEntity();
        } catch (Exception e) {
            logger.error(getUrl + "请求异常", e);
            request.abort();
//            return get(getUrl, header);
        }

        logger.debug("response:" + retVal);
        return retVal;
    }

    /**
     * 从连接池中获取Client
     *
     * @return
     */
    public static CloseableHttpClient getClient() {

        return httpClientBuilder.build();
    }
    public  static boolean hasMaxError(String url){
        if(URL_POST_ERR_COUNT.containsKey(url)){
            if(URL_POST_ERR_COUNT.get(url).intValue()>=Integer.valueOf(Constant.getPropertity("http.post.maxError", 10))){
                URL_POST_ERR_COUNT.remove(url);
                return true;
            }else{
                URL_POST_ERR_COUNT.get(url).incrementAndGet();
            }
        }else{
            URL_POST_ERR_COUNT.put(url, new AtomicInteger(0));
        }

        return false;
    }

}
